package com.jvxb.manage.livable.service.dataAlert.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.Condition;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.jvxb.common.constants.DbConstants;
import com.jvxb.common.constants.WorkOrderConstants;
import com.jvxb.common.utils.BeanUtil;
import com.jvxb.common.utils.CommonUtil;
import com.jvxb.manage.livable.entity.*;
import com.jvxb.manage.livable.service.*;
import com.jvxb.manage.livable.service.dataAlert.RedisDataAlertService;
import com.jvxb.manage.livable.util.RedisChangeUtil;
import com.jvxb.manage.livable.util.WorkOrderUtil;
import com.jvxb.manage.livable.vo.DataAlterVo;
import com.jvxb.manage.livable.vo.WorkOrderOperateVo;
import com.jvxb.modules.configuration.exception.ServiceException;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;

import java.net.Socket;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author jvxb
 * @since 2022-02-09
 */
@Log4j2
@Service(DbConstants.REDIS + DbConstants.DATA_ALERT_SERVICE)
public class RedisDataAlertServiceImpl implements RedisDataAlertService {

    @Autowired
    SysDictService sysDictService;
    @Autowired
    WorkOrderService workOrderService;
    @Autowired
    RedisChangeService redisChangeService;
    @Autowired
    RedisChangeDataService redisChangeDataService;
    @Autowired
    RedisSupportKeyService redisSupportKeyService;
    @Autowired
    AssetsInstanceService assetsInstanceService;


    @Override
    public String checkRedis(Long clusterId, String text) {
        Integer maxCommandCount = Integer.valueOf(sysDictService.getUniValueByGroupAndKey("data_alter_redis", "maxCommandCount", "500"));
        List<String> allCommand = RedisChangeUtil.getAllCommand(text);
        if (allCommand.size() > maxCommandCount) {
            throw new RuntimeException("单个工单最多支持" + maxCommandCount + "条变更命令");
        }
        List<RedisSupportKey> redisSupportKeyDOS = redisSupportKeyService.list(null);
        //所有允许的key
        List<String> supportKeys = redisSupportKeyDOS.stream().map(RedisSupportKey::getKeyName).collect(Collectors.toList());
        Set<String> notSupportKeys = new HashSet<>();
        for (int i = 0; i < allCommand.size(); i++) {
            String command = allCommand.get(i);
            String commandName = RedisChangeUtil.getCommandName(command);
            if (!supportKeys.contains(commandName.toUpperCase())) {
                notSupportKeys.add(commandName);
            } else {
                RedisSupportKey redisSupportKeyDO = redisSupportKeyDOS.stream().filter(o -> o.getKeyName().equalsIgnoreCase(commandName))
                        .findFirst().orElse(null);
                String keyPattern = redisSupportKeyDO.getKeyPattern();
                Pattern compile = Pattern.compile(keyPattern, Pattern.CASE_INSENSITIVE);
                if (!compile.matcher(RedisChangeUtil.prepareCommand(command).getPrepareCommand()).matches()) {
                    throw new RuntimeException("语法错误：" + command + "\r\n位置：第" + (++i) + "行\r\n参照：" + redisSupportKeyDO.getGrammar());
                }
            }
        }
        if (!notSupportKeys.isEmpty()) {
            throw new RuntimeException("不支持的命令:" + notSupportKeys.toString());
        }
        return null;
    }

    @Override
    @Transactional
    public void createOrder(DataAlterVo dataAlterVo) {
        try {
            //1）记录变更信息
            RedisChange redisChange = getRedisChangeInfo(dataAlterVo);
            redisChangeService.save(redisChange);
            //2）拆分变更命令
            List<RedisChangeData> redisChangeDataList = getRedisChangeDataList(redisChange);
            //3）新增变更工单
            WorkOrder workOrder = WorkOrderUtil.createDataAlertOrder(dataAlterVo);
            workOrderService.save(workOrder);
            //关联工单
            redisChange.setWorkOrderId(workOrder.getId());
            redisChangeService.updateById(redisChange);
            if (CommonUtil.notNullOrEmpty(redisChangeDataList)) {
                redisChangeDataList.stream().forEach(e -> e.setWorkOrderId(workOrder.getId()));
                redisChangeDataService.saveBatch(redisChangeDataList);
            }
        } catch (Exception e) {
            log.error(e);
            throw new RuntimeException("创建变更工单异常：" + e.getMessage());
        }
    }

    @Override
    @Async("asyncAlterExecutor")
    public void executeOrder(WorkOrderOperateVo operateVo) {
        RedisChange redisChange = redisChangeService.getOne(Condition.<RedisChange>create()
                .eq(RedisChange.WORK_ORDER_ID, operateVo.getId()).select(RedisChange.ID, RedisChange.CLUSTER_ID, RedisChange.IS_ROLLBACK));
        if (RedisChange.IS_ROLLBACK_YES.equals(redisChange.getIsRollback())) {
            throw new ServiceException("暂不支持回滚工单");
        }

        AssetsInstance redisInstance = assetsInstanceService.getOne(Condition.<AssetsInstance>create().eq(AssetsInstance.CLUSTER_ID, redisChange.getClusterId()).last("limit 1"));
        if (redisInstance == null) {
            throw new RuntimeException("没有可用实例！集群id=" + redisChange.getClusterId());
        }

        //查询未执行、执行失败的命令
        List<RedisChangeData> changeDataList = redisChangeDataService.list(Condition.<RedisChangeData>create()
                .eq(RedisChangeData.WORK_ORDER_ID, operateVo.getId()).last(" and (is_success is null or is_success = 0)"));
        //开始执行时间
        long start = System.currentTimeMillis();
        //执行命令
        String errMsg = execute(changeDataList, redisInstance, getRedisAuth(redisChange.getClusterId()));
        //执行成功
        if (errMsg == null) {
            UpdateWrapper updateWrapper = new UpdateWrapper();
            updateWrapper.eq(RedisChange.ID, redisChange.getId());
            updateWrapper.set(RedisChange.IS_END, RedisChange.IS_END_YES);
            updateWrapper.set(RedisChange.RESPONSE_TIME, System.currentTimeMillis() - start);
            redisChangeService.update(redisChange, updateWrapper);
            workOrderService.executeSuccess(operateVo);
        }
    }

    private String execute(List<RedisChangeData> changeDataList, AssetsInstance redisInstance, String auth) {
        String errMsg = null;
        for (RedisChangeData redisChangeData : changeDataList) {
            try {
                Socket socket = RedisChangeUtil.getSocket(redisInstance.getHost(), Integer.valueOf(redisInstance.getPort()));
                String execute = RedisChangeUtil.execute(socket, redisChangeData.getCommand(), auth);
                if (execute.startsWith("MOVED")) {
                    Socket movedSocket = RedisChangeUtil.movedExecute(execute);
                    execute = RedisChangeUtil.execute(movedSocket, redisChangeData.getCommand(), auth);
                }
                //执行结果
                if (WorkOrderConstants.EXECUTE_SUCCESS.equals(execute)) {
                    redisChangeData.setIsSuccess(RedisChangeData.IS_SUCCESS_YES);
                    redisChangeDataService.updateById(redisChangeData);
                } else {
                    errMsg = "位置" + redisChangeData.getNum() + "执行失败：" + execute;
                    executeFail(redisChangeData, errMsg);
                    break;
                }
            } catch (Exception e) {
                errMsg = String.format("执行变更工单[%s]异常，位置：%s，异常：%s", redisChangeData.getWorkOrderId(), redisChangeData.getNum(), e.getMessage());
                log.error(errMsg, e);
                executeFail(redisChangeData, errMsg);
                break;
            }
        }
        return errMsg;
    }

    public void executeFail(RedisChangeData redisChangeData, String errMsg) {
        //命令执行失败
        redisChangeData.setIsSuccess(RedisChangeData.IS_SUCCESS_NO);
        redisChangeData.setFailDesc(errMsg);
        redisChangeDataService.updateById(redisChangeData);
        //工单执行异常
        workOrderService.executeFail(redisChangeData.getWorkOrderId(), errMsg);
    }

    private RedisChange getRedisChangeInfo(DataAlterVo dataAlterVo) {
        RedisChange redisChange = new RedisChange();
        BeanUtil.copy(dataAlterVo, redisChange);
        redisChange.setCreaterId(dataAlterVo.getCreator().getUserCode());
        redisChange.setCreator(JSON.toJSONString(dataAlterVo.getCreator()));
        redisChange.setIsEnd(RedisChange.IS_END_NO);
        redisChange.setCreateTime(new Date());
        return redisChange;
    }

    private List<RedisChangeData> getRedisChangeDataList(RedisChange redisChange) {
        //如果是回滚工单不拆解命令
        if (redisChange.getIsRollback().equals(RedisChange.IS_ROLLBACK_YES)) {
            return null;
        }
        Map<String, RedisSupportKey> redisSupportKeyMap = new HashMap<>();
        redisSupportKeyService.list(null).stream().forEach(e -> redisSupportKeyMap.put(e.getKeyName(), e));
        List<RedisChangeData> redisChangeDataList = new ArrayList<>();
        String errMsg = null;
        Jedis jedis = null;
        JedisCluster jedisCluster = null;
        String auth = getRedisAuth(redisChange.getClusterId());
        try {
            int num = 1;
            jedis = getClusterJedis(redisChange.getClusterId(), auth);
            jedisCluster = getJedisClusterByJedis(jedis, auth);
            List<String> allCommand = RedisChangeUtil.getAllCommand(redisChange.getCommand());
            for (String command : allCommand) {
                String commandName = RedisChangeUtil.getCommandName(command).toUpperCase();
                RedisSupportKey redisSupportKeyDO = redisSupportKeyMap.get(commandName);
                List<String> keys = RedisChangeUtil.getKey(command, redisSupportKeyDO.getKeyType());
                for (String key : keys) {
                    RedisChangeData redisChangeData = new RedisChangeData();
                    redisChangeData.setCommand(command);
                    redisChangeData.setIsRollback(redisChange.getIsRollback());
                    redisChangeData.setKeyName(key);
                    redisChangeData.setNum(num++);
                    //设置ttl
                    redisChangeData.setTtl(RedisChangeUtil.getTtl(jedis, redisChangeData.getKeyName(), auth));
                    //是否大key
                    redisChangeData.setBigkey(isBigKey(jedis, jedisCluster, key));
                    redisChangeDataList.add(redisChangeData);
                }
            }
        } catch (Exception e) {
            errMsg = e.getMessage();
            log.error("拆解redis命令异常，请联系平台值班：" + errMsg, e);
        } finally {
            if (jedis != null) {
                jedis.close();
            }
            if (jedisCluster != null) {
                RedisChangeUtil.closeJedisCluster(jedisCluster);
            }
        }
        if (errMsg == null && CommonUtil.notNullOrEmpty(redisChangeDataList)) {
            return redisChangeDataList;
        } else {
            throw new RuntimeException("拆解redis命令异常，请联系平台值班：" + errMsg);
        }
    }

    private String getRedisAuth(Long clusterId) {
        return null;
    }

    private Jedis getClusterJedis(Long clusterId, String auth) {
        AssetsInstance redisInstance = assetsInstanceService.getOne(Condition.<AssetsInstance>create().eq(AssetsInstance.CLUSTER_ID, clusterId).last("limit 1"));
        if (redisInstance == null) {
            throw new RuntimeException("没有可用实例！集群id=" + clusterId);
        }
        Jedis jedis = new Jedis(redisInstance.getHost(), Integer.valueOf(redisInstance.getPort()));
        //测试是否需要password，如果没有且获取不到密码
        if (auth != null && auth.length() > 0) {
            jedis.auth(auth);
        }
        return jedis;
    }

    private JedisCluster getJedisClusterByJedis(Jedis jedis, String auth) {
        JedisCluster jedisCluster = null;
        String server = jedis.info("Server");
        String mode = server.contains("redis_mode:standalone") ? "standalone" : "cluster";
        if (mode.equals("cluster")) {
            jedisCluster = getJedisCluster(jedis, auth);
        }
        return jedisCluster;
    }

    private Integer isBigKey(Jedis jedis, JedisCluster jedisCluster, String key) {
        Integer big_string_size = Integer.valueOf(sysDictService.getUniValueByGroupAndKey("redis_data_alter", "big_string_size", "10"));
        Integer big_key_size = Integer.valueOf(sysDictService.getUniValueByGroupAndKey("redis_data_alter", "big_key_size", "10000"));
        try {
            if (jedisCluster != null) {
                String type = jedisCluster.type(key);
                //string不超过10M、其他长度不超过1w
                if (type.equals("string") && jedisCluster.strlen(key) > big_string_size * 1024 * 1024) {
                    return 1;
                } else if (type.equals("list") && jedisCluster.llen(key) > big_key_size) {
                    return 1;
                } else if (type.equals("hash") && jedisCluster.hlen(key) > big_key_size) {
                    return 1;
                } else if (type.equals("set") && jedisCluster.scard(key) > big_key_size) {
                    return 1;
                } else if (type.equals("zset") && jedisCluster.zcard(key) > big_key_size) {
                    return 1;
                }
            } else if (jedis != null) {
                String type = jedis.type(key);
                if (type.equals("string") && jedis.strlen(key) > big_string_size * 1024 * 1024) {
                    return 1;
                } else if (type.equals("list") && jedis.llen(key) > big_key_size) {
                    return 1;
                } else if (type.equals("hash") && jedis.hlen(key) > big_key_size) {
                    return 1;
                } else if (type.equals("set") && jedis.scard(key) > big_key_size) {
                    return 1;
                } else if (type.equals("zset") && jedis.zcard(key) > big_key_size) {
                    return 1;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return 0;
    }

    private JedisCluster getJedisCluster(Jedis jedis, String auth) {
        Set<HostAndPort> hostAndPortSet = new HashSet<>();
        hostAndPortSet.add(new HostAndPort(jedis.getClient().getHost(), jedis.getClient().getPort()));
        JedisCluster jedisCluster =
                (auth == null || auth.length() == 0) ? new JedisCluster(hostAndPortSet) :
                        new JedisCluster(hostAndPortSet, 10000, 10000, 100, auth, new GenericObjectPoolConfig());
        return jedisCluster;
    }
}
